#include "Camera.h"

//Inicialization - "sometimes much more efficient...and with consistency" (Efficient C++, Scott Meyers) 
Camera::Camera (const float &w, const float &h)
:
mouse (w / 2, h / 2),
eye (vec3 (0, 0, 1)), center (vec3 (0, 0, 0)), up (vec3 (0, 1, 0)),
angle (vec3 (0)), position (vec3 (0, -7.0f, -15.0f)),
fov (50.0f), zNear (1.f), zFar (100.0f),
width (w), height (h),
transform (mat4 (1.0f)), view (mat4 (1.0f)), projection (mat4 (1.0f))
{
   moveDir[0] = moveDir[1] = moveDir[2] = moveDir[3] = moveDir[4] = moveDir[5] = false;
   vec3 tmp = normalize (position);
   angle.x = acos (-tmp.z)*180.0f / (float)Pi;
   update ();
}

void Camera::update (void)
{
   movement ();
   projection = perspectiveFov (fov, width, height, zNear, zFar);
   view = lookAt (eye, center, up);
   transform = view*getRotate ()*translate (vec3 (position.x, position.y, position.z));
}

//@TODO - FPS independent movement
void Camera::movement ()
{
   if (moveDir[FWD] == true)
      fowardMov ();

   if (moveDir[BWD] == true)
      backwardMov ();

   if (moveDir[STR] == true)
      strateRightMov ();

   if (moveDir[STL] == true)
      strateLeftMov ();

   if (moveDir[UP] == true)
      flyUpMov ();

   if (moveDir[DOWN] == true)
      flyDownMov ();

   mouseCamMov ();
}
void Camera::mouseCamMov ()
{
   float mx = (width / 2.0f - mouse.x);
   float my = (height / 2.0f - mouse.y);
   float r = glm::sqrt (float(pow (mx, 2) + pow (my, 2)));
   if (r >= 200.0f) {

      float div_x = 1000000.0f / (5.0f*r);
      float div_y = 1000000.0f / (5.0f*r);

      float xMove = (mouse.x - width / 2.0f);
      float yMove = (mouse.y - height / 2.0f);

      angle.x += yMove / (div_x);
      angle.y += xMove / (div_y);

      /*if (angle.x > 65.0f)
         angle.x = 65.0f;
         if (angle.x < -10.0f)
         angle.x = -10.0f;*/
   }
}
void Camera::fowardMov (void)
{
   position.x = position.x + 0.1f*sin (glm::radians (-angle.y));
   position.z = position.z + 0.1f*cos (glm::radians (-angle.y));
}
void Camera::backwardMov (void)
{
   position.x = position.x - 0.05f*sin (glm::radians (-angle.y));
   position.z = position.z - 0.05f*cos (glm::radians (-angle.y));
}
void Camera::strateLeftMov (void)
{
   position.x = position.x - 0.05f*sin (glm::radians (-(angle.y + 90.0f)));
   position.z = position.z - 0.05f*cos (glm::radians (-(angle.y + 90.0f)));
}
void Camera::strateRightMov (void)
{
   position.x = position.x + 0.05f*sin (glm::radians (-(angle.y + 90.0f)));
   position.z = position.z + 0.05f*cos (glm::radians (-(angle.y + 90.0f)));
}
void Camera::flyUpMov (void)
{
   position.y = position.y - 0.1f;
}
void Camera::flyDownMov (void)
{
   position.y = position.y + 0.1f;
}

void Camera::setEye (const vec3 &eyeVec)
{
   eye = eyeVec;
}
void Camera::setCenter (const vec3 &centerVec)
{
   center = centerVec;
}
void Camera::setUp (const vec3 &upVec)
{
   up = upVec;
}
void Camera::setDimension (const float &w, const float &h)
{
   width = w;
   height = h;
}
void Camera::setTransform (const mat4 &w)
{
   transform = w;
}
void Camera::setMouse (const vec2 &m)
{
   mouse = m;
}
void Camera::setMovDir (const int &d)
{
   moveDir[d] = true;
}
void Camera::unSetMovDir (const int &d)
{
   moveDir[d] = false;
}

glm::mat4 Camera::getRotate ()
{
   return glm::rotate (glm::mat4 (1.0f), angle.x, glm::vec3 (1, 0, 0)) *  // X axis
      glm::rotate (glm::mat4 (1.0f), angle.y, glm::vec3 (0, 1, 0)) *  // Y axis
      glm::rotate (glm::mat4 (1.0f), angle.z, glm::vec3 (0, 0, 1));   // Z axis
}
const vec3 &Camera::getEye (void)
{
   return eye;
}
const vec3 &Camera::getCenter (void)
{
   return center;
}
const vec3 &Camera::getUp (void)
{
   return up;
}
const vec3 &Camera::getAngle (void)
{
   return angle;
}
const vec3 &Camera::getPosition (void)
{
   return position;
}
const mat4 &Camera::getProjection (void)
{
   return projection;
}
const mat4 &Camera::getView (void)
{
   return view;
}
const mat4 &Camera::getTransform (void)
{
   return transform;
}
Camera::~Camera ()
{
}
